package name.ekr.assetguide.domain;

import static java.lang.Math.pow;
import static java.lang.String.*;
import static name.ekr.assetguide.Math.sum;

import java.io.IOException;

import name.ekr.assetguide.Math;
import name.ekr.assetguide.data.PriceIterator;
import name.ekr.assetguide.data.DataLogger;
import org.apache.commons.math.stat.descriptive.moment.StandardDeviation;

/**
 * Implements Template Method pattern [GoF]: method evaluate().
 */
public abstract class Portfolio {
	
	//constants:
	protected final double usdShare;
	protected final double goldShare;
	protected final double silverShare;
	protected final double sharesShare;
	protected final double bondsShare;
	protected final double rurShare;

	protected final DataLogger logger;
	protected final PriceIterator iterator;

    //volatiles:
    protected double nUSD;
    protected double nGLD;
    protected double nSLV;
    protected double nSHR;
    protected double nBND;
    protected double nRUR;

    protected StandardDeviation standardDeviation = new StandardDeviation();

    protected long periodsCounter;
    protected double price;
    protected double risk;
    protected double profit;


    /**
	 * Contract:
	 * Invariant: {sum(shares)==1}
	 */
	public Portfolio(
			PriceIterator iterator, DataLogger logger, double usdShare,
			double goldShare, double silverShare, double sharesShare,
			double bondsShare, double rurShare) {

		double sumShares = name.ekr.assetguide.Math.roundTo4OrderDigits(usdShare + goldShare + silverShare + sharesShare + bondsShare + rurShare);
		if( sumShares != 1.0)
			throw new IllegalArgumentException("Invariant: {sum(shares)==1}");

		this.usdShare = usdShare;
		this.goldShare = goldShare;
		this.silverShare = silverShare;
		this.sharesShare = sharesShare;
		this.bondsShare = bondsShare;
		this.rurShare = rurShare;
		this.logger = logger;
		this.iterator = iterator;

	}


    @Override
	public String toString() {
		return  format("%.4f", profit) + ";" + format("%.4f", risk)
//				+   ";USD"   + usdShare      + ";GLD" + goldShare
//				+   ";SLV"  + silverShare   + ";SHR" + sharesShare
//                +   ";BND"  + bondsShare    + ";RUR" + rurShare;
                +   ";"   + usdShare      + ";" + goldShare
                +   ";"  + silverShare   + ";" + sharesShare
                +   ";"  + bondsShare    + ";" + rurShare;
	}

	public void evaluate() throws IOException {
		AssetsPrice prices;

        try {
            prices = iterator.getNextPrices();
            zeroPeriodHandler(prices);
        } catch (TimeBoundsException e) {
            iterator.reset();
        }

		do
            try {
			    prices = iterator.getNextPrices();
			    everyPeriodHandler(prices);
		    } catch (TimeBoundsException e) {
                iterator.reset();
			    break;
		    }
        while (true);

        wholePeriodHandler();
		logger.info(toString());
	}


    protected void everyPeriodHandler(AssetsPrice prices) {
        calculatePriceAndRisk(prices);
        periodsCounter++;
    }


    private void zeroPeriodHandler(AssetsPrice prices) {
        nUSD = usdShare / prices.getUsdPrice();
        nGLD = goldShare / prices.getGoldPrice();
        nSLV = silverShare / prices.getSilverPrice();
        nSHR = sharesShare / prices.getSharesPrice();
        nBND = bondsShare / prices.getBondsPrice();
        nRUR = rurShare / prices.getRurPrice();

        this.price = 1;
    }

	private void wholePeriodHandler() {
        this.profit = Math.roundTo4OrderDigits(
                pow(this.price, 1.0 / this.periodsCounter) - 1.0
        );
		this.risk = Math.roundTo4OrderDigits(standardDeviation.getResult());
	}

    private void calculatePriceAndRisk(AssetsPrice prices) {
        double oldPrice = this.price;
        price = sum(
                nUSD * prices.getUsdPrice(),
                nGLD * prices.getGoldPrice(),
                nSLV * prices.getSilverPrice(),
                nSHR * prices.getSharesPrice(),
                nBND * prices.getBondsPrice(),
                nRUR * prices.getRurPrice()
        );
        standardDeviation.increment((price - oldPrice) / oldPrice);
    }

}






















